#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "imbus_internal.h"
#include "im_array.h"
#include "im_hash.h"
#include "im_hotkeys.h"

/************************** Declaration of internal functions ***************/
static void     __im_hotkey_initialize          (IMPointer       hotkey);
static void     __im_hotkey_finalize            (IMPointer       hotkey);
static void     __im_hotkey_copy                (IMPointer       dest,
                                                 IMConstPointer  src);
static IMBool   __im_hotkey_serialize           (IMConstPointer  obj,
                                                 IMPointer       stream);
static IMBool   __im_hotkey_deserialize         (IMPointer       obj,
                                                 IMPointer       stream);
static void     __im_hotkey_class_init          (IMPointer       klass);

static void     __im_hotkey_profile_initialize  (IMPointer       profile);
static void     __im_hotkey_profile_finalize    (IMPointer       profile);
static void     __im_hotkey_profile_copy        (IMPointer       dest,
                                                 IMConstPointer  src);
static IMBool   __im_hotkey_profile_serialize   (IMConstPointer  obj,
                                                 IMPointer       stream);
static IMBool   __im_hotkey_profile_deserialize (IMPointer       obj,
                                                 IMPointer       stream);
static void     __im_hotkey_profile_class_init  (IMPointer       klass);

/****************** Declaration of object and class structure ************/
struct _IMHotkey
{
    IMObject object;

    /* private */
    IMUInt32         keycode;
    IMPtrArray      *events;
};

struct _IMHotkeyClass
{
    IMObjectClass parent_class;
};

struct _IMHotkeyProfile
{
    IMObject object;

    /* private */
    IMUInt32                 id;
    IMHotkeyProfileScope     scope;
    IMHashTable             *hotkeys;
};

struct _IMHotkeyProfileClass
{
    IMObjectClass parent_class;
};

/************************ Internal varable ************************/
static const IMObjectClass *__im_hotkey_parent_class = 0;
static const IMObjectClass *__im_hotkey_profile_parent_class = 0;

/************* Implementation of internal functions ***************/
/* IMHotkey */
static void
__im_hotkey_initialize (IMPointer hotkey)
{
    ((IMHotkey*)hotkey)->keycode = 0;
    ((IMHotkey*)hotkey)->events  = 0;
}

static void
__im_hotkey_finalize (IMPointer hotkey)
{
    IMSize i;
    IMHotkey *p = IM_HOTKEY (hotkey);

    _im_assert (p != 0);

    if (p->events) {
        for (i=0; i < im_ptr_array_size (p->events); ++i)
            im_object_unref (im_ptr_array_index (p->events, i));

        im_ptr_array_destroy (p->events, TRUE);
    }

    p->events = 0;
}

static void
__im_hotkey_copy (IMPointer dest, IMConstPointer src)
{
    IMSize i;
    IMHotkey       *dp = IM_HOTKEY (dest);
    const IMHotkey *sp = IM_CONST_HOTKEY (src);

    _im_assert (dp && sp);

    __im_hotkey_finalize (dest);

    dp->keycode     = sp->keycode;

    if (sp->events && im_ptr_array_size (sp->events)) {
        dp->events = im_ptr_array_new_reserved (im_ptr_array_size (sp->events));
        _im_assert (dp->events);
        for (i = 0; i < im_ptr_array_size (sp->events); ++i)
            im_ptr_array_append (dp->events,
                im_object_ref (im_object_clone (im_ptr_array_index (sp->events, i))));
    }

    /* Call copy method of parent class */
    __im_hotkey_parent_class->copy (dest, src);
}

static IMBool
__im_hotkey_serialize (IMConstPointer obj, IMPointer stream)
{
    const IMHotkey *kp = IM_CONST_HOTKEY (obj);
    IMStream       *sp = IM_STREAM (stream);
    IMSize i;

    _im_assert (kp && sp);

    /* Call parent serialize method */
    if (!__im_hotkey_parent_class->serialize (obj, stream))
        return FALSE;

    if (!im_stream_put_uint32 (sp, kp->keycode))
        return FALSE;
    if (!im_stream_put_uint32 (sp, (kp->events ? im_ptr_array_size (kp->events) : 0)))
        return FALSE;

    if (kp->events) {
        for (i = 0; i < im_ptr_array_size (kp->events); ++i)
            if (!im_stream_put_object (sp, im_ptr_array_index (kp->events, i)))
                return FALSE;
    }
    return TRUE;
}

static IMBool
__im_hotkey_deserialize (IMPointer obj, IMPointer stream)
{
    IMHotkey *kp = IM_HOTKEY (obj);
    IMStream *sp = IM_STREAM (stream);
    IMUInt32  keycode;
    IMUInt32  num;
    IMSize    i;
    IMPointer event;

    _im_assert (kp && sp);

    /* Call parent deserialize method */
    if (!__im_hotkey_parent_class->deserialize (obj, stream))
        return FALSE;

    if (!im_stream_get_uint32 (sp, &keycode) || !im_stream_get_uint32 (sp, &num))
        return FALSE;

    __im_hotkey_finalize (obj);

    kp->keycode = keycode;

    if (num) {
        kp->events = im_ptr_array_new_reserved (num);
        _im_assert (kp->events);
        for (i = 0; i < num; ++i) {
            if (im_stream_get_data_type (sp) != IM_TYPE_EVENT)
                return FALSE;

            event = im_object_ref (im_object_new (IM_TYPE_EVENT));
            _im_assert (event);

            if (!im_stream_get_object (sp, event)) {
                im_object_unref (event);
                return FALSE;
            }

            im_ptr_array_append (kp->events, event);
        }
    }

    return TRUE;
}

static void
__im_hotkey_class_init (IMPointer klass)
{
    IMObjectClass *cp = (IMObjectClass *)klass;

    cp->copy        = __im_hotkey_copy;
    cp->serialize   = __im_hotkey_serialize;
    cp->deserialize = __im_hotkey_deserialize;

    __im_hotkey_parent_class = im_object_class_get_parent (cp);
}

/* IMHotkeyProfile */
static void
__im_hotkey_profile_initialize (IMPointer profile)
{
    ((IMHotkeyProfile *) profile)->id       = 0;
    ((IMHotkeyProfile *) profile)->scope    = IM_HOTKEY_PROFILE_GLOBAL;
    ((IMHotkeyProfile *) profile)->hotkeys  = 0;
}

static void
__im_hotkey_profile_finalize (IMPointer proplist)
{
    IMHotkeyProfile *p = IM_HOTKEY_PROFILE (proplist);

    _im_assert (p);

    if (p->hotkeys)
        im_hash_table_destroy (p->hotkeys);

    p->hotkeys = 0;
}

static IMBool
__foreach_copy_hotkeys (IMPointer key, IMPointer value, IMPointer table)
{
    im_hash_table_insert ((IMHashTable *)table, key, im_object_ref (im_object_clone (value)));
    return TRUE;
}

static void
__im_hotkey_profile_copy (IMPointer dest, IMConstPointer src)
{
    IMHotkeyProfile       *dp = IM_HOTKEY_PROFILE (dest);
    const IMHotkeyProfile *sp = IM_CONST_HOTKEY_PROFILE (src);

    _im_assert (dp && sp);

    __im_hotkey_profile_finalize (dest);

    dp->id    = sp->id;
    dp->scope = sp->scope;

    if (sp->hotkeys) {
        dp->hotkeys = im_hash_table_new_full (im_direct_hash,
                                              0,
                                              0,
                                              im_object_unref);

        _im_assert (dp->hotkeys);

        im_hash_table_foreach (sp->hotkeys, __foreach_copy_hotkeys, dp->hotkeys);
    }

    /* Call copy method of parent class */
    __im_hotkey_profile_parent_class->copy (dest, src);
}

static IMBool
__foreach_serialize_hotkeys (IMPointer key, IMPointer value, IMPointer stream)
{
    return im_stream_put_object ((IMStream*) stream, value);
}

static IMBool
__im_hotkey_profile_serialize (IMConstPointer obj, IMPointer stream)
{
    const IMHotkeyProfile *pp = IM_CONST_HOTKEY_PROFILE (obj);
    IMStream              *sp = IM_STREAM (stream);

    _im_assert (pp && sp);

    /* Call parent serialize method */
    if (!__im_hotkey_profile_parent_class->serialize (obj, stream))
        return FALSE;

    if (!im_stream_put_uint32 (sp, pp->id))
        return FALSE;
    if (!im_stream_put_uint32 (sp, pp->scope))
        return FALSE;
    if (!im_stream_put_uint32 (sp, (pp->hotkeys ? im_hash_table_size (pp->hotkeys) : 0)))
        return FALSE;

    if (pp->hotkeys && im_hash_table_size (pp->hotkeys))
        return im_hash_table_foreach (pp->hotkeys, __foreach_serialize_hotkeys, stream);

    return TRUE;
}

static IMBool
__im_hotkey_profile_deserialize (IMPointer obj, IMPointer stream)
{
    IMHotkeyProfile *pp = IM_HOTKEY_PROFILE (obj);
    IMStream        *sp = IM_STREAM (stream);
    IMUInt32  id;
    IMUInt32  scope;
    IMUInt32  num;
    IMSize    i;
    IMPointer hotkey;

    /* Call parent deserialize method */
    if (!__im_hotkey_profile_parent_class->deserialize (obj, stream))
        return FALSE;

    if (!im_stream_get_uint32 (sp, &id) ||
        !im_stream_get_uint32 (sp, &scope) ||
        !im_stream_get_uint32 (sp, &num))
        return FALSE;

    /* clear this property list */
    __im_hotkey_profile_finalize (pp);

    pp->id    = id;
    pp->scope = (IMHotkeyProfileScope) scope;

    if (num) {
        pp->hotkeys = im_hash_table_new_full (im_direct_hash,
                                              0,
                                              0,
                                              im_object_unref);
        _im_assert (pp->hotkeys);

        for (i = 0; i < num; ++i) {
            if (!im_stream_get_data_type (sp) != IM_TYPE_HOTKEY)
                return FALSE;

            hotkey = im_object_ref (im_object_new (IM_TYPE_HOTKEY));
            _im_assert (hotkey);

            if (!im_stream_get_object (sp, hotkey)) {
                im_object_unref (hotkey);
                return FALSE;
            }

            im_hash_table_insert (pp->hotkeys,
                                  IM_UINT_TO_POINTER (im_hotkey_get_key_code ((IMHotkey*)hotkey)),
                                  hotkey);
        }
    }

    return TRUE;
}

static void
__im_hotkey_profile_class_init (IMPointer klass)
{
    IMObjectClass *cp = (IMObjectClass *) klass;

    cp->copy        = __im_hotkey_profile_copy;
    cp->serialize   = __im_hotkey_profile_serialize;
    cp->deserialize = __im_hotkey_profile_deserialize;

    __im_hotkey_profile_parent_class = im_object_class_get_parent (cp);
}

/************* Implementation of public functions ***************/
/* IMHotkey */
void
__im_type_register_hotkey ()
{
    static IMTypeInfo im_hotkey_type_info = 
    {
        sizeof (IMHotkeyClass),   /**< class_size >**/
        __im_hotkey_class_init,   /**< class_init >**/
        0,                          /**< class_finalize >**/

        sizeof (IMHotkey),        /**< instance_size >**/
        __im_hotkey_initialize,   /**< instance_init >**/
        __im_hotkey_finalize,     /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_OBJECT,
                                      IM_TYPE_HOTKEY,
                                      "Hotkey",
                                      &im_hotkey_type_info,
                                      TRUE);
}

IMHotkey*
im_hotkey_new (IMUInt32 keycode)
{
    IMHotkey *hotkey = (IMHotkey*) im_object_new (IM_TYPE_HOTKEY);
    _im_assert (hotkey);

    hotkey->keycode = keycode;
    return hotkey;
}

void
im_hotkey_set_key_code (IMHotkey *hotkey, IMUInt32 keycode)
{
    _im_return_if_fail (IM_IS_HOTKEY(hotkey));

    hotkey->keycode = keycode;
}

IMUInt32
im_hotkey_get_key_code (const IMHotkey *hotkey)
{
    _im_return_val_if_fail (IM_IS_HOTKEY(hotkey), 0);
    return hotkey->keycode;
}

IMSize
im_hotkey_get_n_events (const IMHotkey *hotkey)
{
    _im_return_val_if_fail (IM_IS_HOTKEY(hotkey), 0);
    return hotkey->events ? im_ptr_array_size (hotkey->events) : 0;
}

void
im_hotkey_clear (IMHotkey *hotkey)
{
    _im_return_if_fail (IM_IS_HOTKEY(hotkey));
    __im_hotkey_finalize (hotkey);
}

void
im_hotkey_append_event (IMHotkey *hotkey, IMEvent *event)
{
    _im_return_if_fail (IM_IS_HOTKEY(hotkey));
    _im_return_if_fail (IM_IS_EVENT(event));

    if (!hotkey->events)
        hotkey->events = im_ptr_array_new ();

    _im_assert (hotkey->events);

    im_object_ref (event);
    im_ptr_array_append (hotkey->events, event);
}

IMEvent*
im_hotkey_get_event (const IMHotkey *hotkey, IMSize index)
{
    _im_return_val_if_fail (IM_IS_HOTKEY(hotkey), 0);
    _im_return_val_if_fail (hotkey->events && index < im_ptr_array_size (hotkey->events), 0);

    return im_ptr_array_index (hotkey->events, index);
}

void
im_hotkey_foreach_event (const IMHotkey *hotkey, IMFunc func, IMPointer user_data)
{
    _im_return_if_fail (IM_IS_HOTKEY(hotkey));
    _im_return_if_fail (func);

    if (hotkey->events)
        im_ptr_array_foreach (hotkey->events, func, user_data);
}


/* IMHotkeyProfile */
void
__im_type_register_hotkey_profile ()
{
    static IMTypeInfo im_hotkey_profile_type_info = 
    {
        sizeof (IMHotkeyProfileClass),   /**< class_size >**/
        __im_hotkey_profile_class_init,  /**< class_init >**/
        0,                          /**< class_finalize >**/

        sizeof (IMHotkeyProfile),        /**< instance_size >**/
        __im_hotkey_profile_initialize,  /**< instance_init >**/
        __im_hotkey_profile_finalize,    /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_OBJECT,
                                      IM_TYPE_HOTKEY_PROFILE,
                                      "HotkeyProfile",
                                      &im_hotkey_profile_type_info,
                                      TRUE);
}

IMHotkeyProfile *
im_hotkey_profile_new (IMUInt32 id, IMHotkeyProfileScope scope)
{
    IMHotkeyProfile *profile = (IMHotkeyProfile *) im_object_new (IM_TYPE_HOTKEY_PROFILE);
    _im_assert (profile != 0);

    profile->id = id;
    profile->scope = scope;

    return profile;
}

void
im_hotkey_profile_set_id (IMHotkeyProfile *profile, IMUInt32 id)
{
    _im_return_if_fail (IM_IS_HOTKEY_PROFILE (profile));
    profile->id = id;
}

IMUInt32
im_hotkey_profile_get_id (const IMHotkeyProfile  *profile)
{
    _im_return_val_if_fail (IM_IS_HOTKEY_PROFILE (profile), 0);
    return profile->id;
}

void
im_hotkey_profile_set_scope (IMHotkeyProfile *profile, IMHotkeyProfileScope scope)
{
    _im_return_if_fail (IM_IS_HOTKEY_PROFILE (profile));
    profile->scope = scope;
}

IMHotkeyProfileScope
im_hotkey_profile_get_scope (const IMHotkeyProfile *profile)
{
    _im_return_val_if_fail (IM_IS_HOTKEY_PROFILE (profile), 0);
    return profile->scope;
}

void
im_hotkey_profile_clear (IMHotkeyProfile *profile)
{
    _im_return_if_fail (IM_IS_HOTKEY_PROFILE (profile));
    __im_hotkey_profile_finalize (profile);
}

IMBool
im_hotkey_profile_empty (const IMHotkeyProfile *profile)
{
    _im_return_val_if_fail (IM_IS_HOTKEY_PROFILE (profile), TRUE);

    return profile->hotkeys == 0 || im_hash_table_size (profile->hotkeys) == 0;
}

void
im_hotkey_profile_add (IMHotkeyProfile *profile, IMHotkey *hotkey)
{
    _im_return_if_fail (IM_IS_HOTKEY_PROFILE(profile));
    _im_return_if_fail (IM_IS_HOTKEY(hotkey));

    if (!profile->hotkeys)
        profile->hotkeys = im_hash_table_new_full (im_direct_hash,
                                                   0,
                                                   0,
                                                   im_object_unref);

    _im_assert (profile->hotkeys);

    im_object_ref (hotkey);

    im_hash_table_insert (profile->hotkeys,
                          IM_UINT_TO_POINTER (im_hotkey_get_key_code (hotkey)),
                          hotkey);
}

void
im_hotkey_profile_remove (IMHotkeyProfile *profile, IMUInt32 keycode)
{
    _im_return_if_fail (IM_IS_HOTKEY_PROFILE(profile));

    if (profile->hotkeys)
        im_hash_table_remove (profile->hotkeys, IM_UINT_TO_POINTER (keycode));
}

IMHotkey*
im_hotkey_profile_lookup (const IMHotkeyProfile *profile, IMUInt32 keycode)
{
    _im_return_val_if_fail (IM_IS_HOTKEY_PROFILE(profile), 0);

    return profile->hotkeys ? im_hash_table_lookup (profile->hotkeys, IM_UINT_TO_POINTER (keycode)) : 0;
}

typedef struct 
{
    IMFunc    func;
    IMPointer user_data;
} __IMHotkeyProfileForeachData;

static IMBool
__foreach_hotkey (IMPointer key, IMPointer value, IMPointer u)
{
    ((__IMHotkeyProfileForeachData*)u)->func (value, ((__IMHotkeyProfileForeachData*)u)->user_data);
    return TRUE;
}

void
im_hotkey_profile_foreach (const IMHotkeyProfile *profile, IMFunc func, IMPointer user_data)
{
    __IMHotkeyProfileForeachData data;

    _im_return_if_fail (IM_IS_HOTKEY_PROFILE (profile));
    _im_return_if_fail (func != 0);

    if (profile->hotkeys) {
        data.func = func;
        data.user_data = user_data;
        im_hash_table_foreach (profile->hotkeys, __foreach_hotkey, &data);
    }
}


/*
vi:ts=4:nowrap:ai:expandtab
*/
